iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0

今天我們要來看一個DB問題,當我們執行查詢取得記錄列表,然後對其中每筆記錄執行另一個查詢時,就會發生這種情況。

我們可能會想說許多小查詢會比較快,而一個複雜的大查詢會很慢,但實際上恰好相反,每個查詢都需要向資料庫訪問,資料庫執行查詢後再將結果返回應用程式。執行的查詢越多,返回結果所需的時間越長,因為每次訪問資料庫都會消耗時間和資源。相比之下,單一的複雜查詢可以藉由資料庫伺服器優化,只需要一次訪問,通常就比多次小查詢更快。

N+1問題範例

假設今天我們有兩個表,User 以及 Game,每一個 User 可能有多款 Games,就是要玩收藏遊戲的遊戲而不是玩遊戲(?,當我們想查詢所有User以及他麾下的 Games 時,可能會寫出這樣的query。

users = User.query.all()
for user in users:
    games = Game.query.filter_by(user_id=user.id).all()

這段程式會執行一次查詢來取得所有 User,隨後又進行了 N 次查詢來找出每個 User 的 Games,這就是 N+1 問題。

我們該怎麼解決這N+1問題?

使用關聯查詢 (Eager Loading)

大多數 ORM 提供了 eager loading 的功能,允許我們一次性將相關的資料載入,避免每次訪問關聯數據時都執行額外查詢。

可以參考 https://tw.kotlin.tips/articles/day-19-n1-eager-loading

批量查詢 (Batch Fetching)

將多個查詢合併為一個查詢,或者一次性加載多個記錄,減少查詢次數。某些 ORM 支援設定批量加載關聯數據的大小。
以java的Hibernate為範例,如果不希望在每次查詢中都使用 JOIN FETCH,可以在entity使用 Hibernate 的 @BatchSize annotation來載入關聯數據。

@Entity
public class Author {
    // ...

    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
    @BatchSize(size = 10) // 每次最多加載 10 本書
    private List<Book> books;

    // ...
}

使用原生 SQL 查詢

如果 ORM 的查詢優化功能有限,或者不容易優化,可以考慮直接寫 SQL 查詢,這樣可以更精確地控制查詢的執行方式,避免 N+1 問題。

Caching (緩存)

有些情況下,可以通過緩存已經加載過的數據來減少查詢次數。ORM 本身可能也支持緩存機制。
但要注意caching的一些事情,例如一致性問題或是TTL等。

總結

為了效能考量,要避免 N+1 問題,並且小心地使用ORM,並不是每一種狀況都可以直接使用~ 這邊還是要經驗的累積。

reference

https://hackmd.io/@were1122was/BkUpHXuAq

https://planetscale.com/blog/what-is-n-1-query-problem-and-how-to-solve-it


上一篇
Day 19 DB - ORMs(Object-Relational Mappers)
下一篇
Day 21 Database Normalization
系列文
Backend Developer的學習Roadmap30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言